#region Usage
/* Registration:
 * c:/> regasm /codebase SRCodeGenerator.CustomTool.dll
 * 
 * Unregistration:
 * c:/> regasm /unregister SRCodeGenerator.CustomTool
 *
 * Usage:
 * Add an .resx file to the project and set:
 *  Custom Tool: SRCodeGen
 */
#endregion Usage

#region using

using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Xml;
using CustomToolGenerator;
using EnvDTE;
using Microsoft.Win32;

#endregion

namespace SRGenerator.CustomTool 
{
  /// <summary>
  /// Translates code.
  /// </summary>
  /// <remarks>
  /// Provides helper methods for the C# and VB derived generators.
  /// </remarks>
  [Guid( "75EB777F-AEBA-492a-8A66-FAEA696086DE" )]
  [ComVisible( true )]
  public class SRCodeGenerator : BaseCodeGeneratorWithSite, IProcessorFeedback
  {
    #region Constants

    /// <summary>
    /// VS Generator Category for C# Language.
    /// </summary>
    private static Guid CSharpCategory = new Guid( "{FAE04EC1-301F-11D3-BF4B-00C04F79EFBC}" );

    /// <summary>
    /// VS Generator Category for VB Language.
    /// </summary>
    private static Guid VBCategory = new Guid( "{164B10B9-B200-11D0-8C61-00A0C91E29D5}" );

    /// <summary>
    /// The tool Guid. Keep in sync with the GuidAttribute!!!
    /// </summary>
    private static Guid CustomToolGuid = new Guid( "75EB777F-AEBA-492a-8A66-FAEA696086DE" );

    /// <summary>
    /// Name of the custom tool to register.
    /// </summary>
    private const string CustomToolName = "SRCodeGen";

    /// <summary>
    /// Name of the custom tool to register.
    /// </summary>
    private const string AlternateCustomToolName = "StringResourceTool";

    /// <summary>
    /// Description for registration.
    /// </summary>
    private const string CustomToolDescription = "String Resource Class Generator";

    #endregion Constants

    public SRCodeGenerator()
    {
    }

    #region GenerateCode

    ProjectItem _projItem = null;
        
    /// <summary>
    /// Generates the output.
    /// </summary>
    protected override byte[] GenerateCode( string inputFileName, string inputFileContent )
    {
      string code = "";
      try 
      {
        _projItem = (ProjectItem)GetService(typeof(ProjectItem));

        Processor p = new Processor(CodeProvider);

        if (Path.GetExtension(inputFileName).ToLower() != ".resx")
        {
          // Remove all .resx files
          for (int i=_projItem.ProjectItems.Count; i > 0 ; i--)
          {
            if (Path.GetExtension(_projItem.ProjectItems.Item(i).Name).ToLower() == ".resx")
            {
              _projItem.ProjectItems.Item(i).Delete();
            }
          }


          inputFileName = p.GenerateResourceFile(inputFileName, this);
          if (inputFileName == null)
          {
            return null;
          }
        }

        bool hasCustomToolNamespace = false;

        string CustomToolNamespace = (string) GetProperty(_projItem.Properties, "CustomToolNamespace");
        if (!String.IsNullOrEmpty(CustomToolNamespace))
        {
          hasCustomToolNamespace = true;
        }

        StringBuilder sb = p.GenerateCode(inputFileName, FileNameSpace, this, hasCustomToolNamespace);

        // Finaly assign it to the result to return.
        code = sb.ToString();
      }
      catch( Exception e ) 
      {
        code = String.Format( "#error Couldn't generate code!\n/*\n{0}\n*/", e );
      }
      // Convert to bytes.
      return Encoding.UTF8.GetBytes( code );
    }

    #endregion GenerateCode

    #region Registration

    // [HKLM\SOFTWARE\Microsoft\VisualStudio\{Version}\Generators\{C#/VB GUID}\{ToolName}]
    private const string KeyFormat = @"SOFTWARE\Microsoft\VisualStudio\{0}\Generators\{1}\{2}";

    /// <summary>
    /// Registers the tool for a VS version and a language category.
    /// </summary>
    protected static void Register( Version vsVersion, Guid categoryGuid ) 
    {
      /* Key to create:
       * 
        * [HKLM\SOFTWARE\Microsoft\VisualStudio\{Version}\Generators\{C#/VB GUID}\{ToolName}]
        * @="{ToolDescription}"
        * "CLSID"="{ToolGUID}"
        * "GeneratesDesignTimeSource"=dword:00000001
        */

      using( RegistryKey key = Registry.LocalMachine.CreateSubKey( String.Format( 
               KeyFormat, vsVersion, categoryGuid.ToString( "B" ), CustomToolName ) ) )
      {
        key.SetValue( "", CustomToolDescription );
        key.SetValue( "CLSID", CustomToolGuid.ToString( "B" ) );
        key.SetValue( "GeneratesDesignTimeSource", 1 );
      }

      using( RegistryKey key = Registry.LocalMachine.CreateSubKey( String.Format( 
               KeyFormat, vsVersion, categoryGuid.ToString( "B" ), AlternateCustomToolName ) ) )
      {
        key.SetValue( "", CustomToolDescription );
        key.SetValue( "CLSID", CustomToolGuid.ToString( "B" ) );
        key.SetValue( "GeneratesDesignTimeSource", 1 );
      }
    }

    /// <summary>
    /// Unregisters the custom tool.
    /// </summary>
    protected static void Unregister( Version vsVersion, Guid categoryGuid ) 
    {
      Registry.LocalMachine.DeleteSubKey( 
        String.Format( KeyFormat, vsVersion, categoryGuid.ToString( "B" ), CustomToolName ), false );

      Registry.LocalMachine.DeleteSubKey( 
        String.Format( KeyFormat, vsVersion, categoryGuid.ToString( "B" ), AlternateCustomToolName ), false );
    }

    /// <summary>
    /// Registers the generator.
    /// </summary>
    [ComRegisterFunction]
    public static void RegisterClass( Type t ) 
    {
      //// Register for both VS.NET 2002 & 2003 ( C# )
      //Register( new Version( 7, 0 ), CSharpCategory );
      //Register( new Version( 7, 1 ), CSharpCategory );

      //// Register for both VS.NET 2002 & 2003 ( VB )
      //Register( new Version( 7, 0 ), VBCategory );
      //Register( new Version( 7, 1 ), VBCategory );

      // Register for VS.NET 2008 ( C# & VB )
      Register(new Version(9, 0), CSharpCategory);
      Register(new Version(9, 0), VBCategory);

      // Register for VS.NET 2010 ( C# & VB )
      Register(new Version(10, 0), CSharpCategory);
      Register(new Version(10, 0), VBCategory);

    }

    /// <summary>
    /// Unregisters the generator.
    /// </summary>
    [ComUnregisterFunction]
    public static void UnregisterClass( Type t ) 
    {
      //// Unregister for both VS.NET 2002 & 2003 ( C# )
      //Unregister( new Version( 7, 0 ), CSharpCategory );
      //Unregister( new Version( 7, 1 ), CSharpCategory );

      //// Unregister for both VS.NET 2002 & 2003 ( VB )
      //Unregister( new Version( 7, 0 ), VBCategory );
      //Unregister( new Version( 7, 1 ), VBCategory );

      // Unregister for VS.NET 2008 ( C# & VB )
      Unregister(new Version(9, 0), CSharpCategory);
      Unregister(new Version(9, 0), VBCategory);

      // Unregister for VS.NET 2010 ( C# & VB )
      Unregister(new Version(10, 0), CSharpCategory);
      Unregister(new Version(10, 0), VBCategory);

    }

    #endregion Registration

    #region IProcessorFeedback Members

    public void LineError(string message, string line, int lineNo)
    {
      CodeGeneratorProgress.GeneratorError(true, 0, message + " on line " + lineNo.ToString(), lineNo, 1);
    }

    public void Information(string message)
    {
      // Ignore
    }

    public void AddResourceFile(string fileName)
    {
      string resFilename = Path.GetFileName(fileName);

      ProjectItem baseProjectItem = _projItem;

      // See if the resource file is already in the items collection for this file.
      ProjectItem resProjItem = null;
      for (int i=1; i <= baseProjectItem.ProjectItems.Count; i++)
      {
        ProjectItem pi = baseProjectItem.ProjectItems.Item(i);
        if (pi.Name == resFilename)
        {
          resProjItem = baseProjectItem.ProjectItems.Item(i);
          break;
        }
      }
      if (resProjItem == null)
      {
        resProjItem = baseProjectItem.ProjectItems.AddFromFile(fileName);
      }
    }

    #endregion

    private object GetProperty(Properties props, string name)
    {
      for (int i=1; i < props.Count; i++)
      {
        if (props.Item(i).Name == name)
        {
          return props.Item(i).Value;
        }
      }

      return null;
    }
  }
}